package com.base.cn.platform.os.common.interceptor;

import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.base.cn.platform.os.common.annotation.ImgAttr;
import com.base.cn.platform.os.common.mybatis.BaseEntity;
import com.base.cn.platform.os.common.utils.SnowFlake;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * mybatis的Sql拦截器
 * @author s.li
 * @create 2018-04-11-15:17
 */
@Slf4j
@Intercepts(
        value = {
                @Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,CacheKey.class, BoundSql.class}),
                @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
        }
)
@Component
public class MyBatisSqlInterceptor implements Interceptor {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * #启动图片路径域名替换
     */
    @Value("${img.replace.domain.name.enabled:false}")
    private boolean imgReplaceEnabled;
    /**
     * 要替换跳的域名，多个域名以“,”隔开
     */
    @Value("${img.replace.old.domain.name}")
    private String imgReplaceOldDomainName;
    /**
     * 替换成的目标域名
     */
    @Value("${img.replace.target.domain.name}")
    private String imgReplaceTargetDomainName;

    private static final String PARAM_KEY = "id";
    private Object parameterObject;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //参数组
        Object[] args = invocation.getArgs();
        //应映射流
        MappedStatement mappedStatement = (MappedStatement) args[0];
        //SQL操作类型（SELECT,DELETE,UPDATE,INSERT）
        SqlCommandType type = mappedStatement.getSqlCommandType();
        String id = mappedStatement.getId();
        if(id.endsWith("Dao.getID")){
            return invocation.proceed();
        }
        //查询的SQL参数
        Object parameterObject = args[1];
        //查询的SQL语句
        BoundSql boundSql;
        //有一些操作args属性只有4个或2个元素（删除、添加、修改只有两个参数）
        if(args.length == 4 || args.length==2){
            //4 个参数时
            boundSql = mappedStatement.getBoundSql(parameterObject);
        } else {//查询的时候有6个元素
            //6 个参数时
            boundSql = (BoundSql) args[5];
        }
        //如果SQL不存在
        if (boundSql == null || StringUtils.isEmpty(boundSql.getSql())){
            return null;
        }
//        //如果是添加操作，生成数据唯一ID
//        if(type == SqlCommandType.INSERT ){
//            insertSnowFlakeId(invocation,mappedStatement,boundSql,parameterObject);
//        }
        //替换返回结果的图片路径
        this.replaceImgUrl(type,invocation);
        //返回操作结果
        Object result = invocation.proceed();
        return result;
    }

    /**
     * 处理添加语句，使用雪花算法生成ID
     * @return
     */
    private void insertSnowFlakeId(Invocation invocation,MappedStatement mappedStatement,BoundSql boundSql,Object parameterObject){
        //SQL解析器
        MySqlStatementParser parser = new MySqlStatementParser(boundSql.getSql());
        //SQL对象
        SQLStatement sqlStatement = parser.parseStatement();
        MySqlInsertStatement insertStatement = (MySqlInsertStatement)sqlStatement;
        SnowFlake snowFlake = new SnowFlake(1,1);
        List<SQLInsertStatement.ValuesClause> valueList = insertStatement.getValuesList();
        insertStatement.addColumn(new SQLIdentifierExpr("`id`"));
        valueList.get(0).addValue(new SQLIdentifierExpr(String.valueOf(snowFlake.nextId())));
        BoundSql _boundSql = getBoundSqlWrapper(mappedStatement, boundSql, sqlStatement.toString(), parameterObject);
        //获取新的映射数据
        MappedStatement newMs = getMappedStatement(mappedStatement,_boundSql,sqlStatement.toString(),parameterObject);
        //如果只是添加、修改、删除只重新设置元素就可以
        invocation.getArgs()[0] = newMs;
//        //如果是查询必须也要重新设置这个元素
//        if(invocation.getArgs().length==6) {
//            //如果只是添加、修改、删除只重新设置元素就可以
//            invocation.getArgs()[0] = newMs;
//            //如果是查询必须也要重新设置这个元素
//            if (invocation.getArgs().length == 6) {
//                invocation.getArgs()[5] = _boundSql;
//            }
//        }
    }
    /**
     * 替换返回结果的图片路径
     * @param type 操作类型，只对Select类型走作用
     * @param invocation 返回结果
     * @throws Throwable
     */
    private void replaceImgUrl(SqlCommandType type,Invocation invocation) throws Throwable{
        try{
            if(type.equals(SqlCommandType.SELECT) && imgReplaceEnabled
                    && imgReplaceOldDomainName!=null && !imgReplaceOldDomainName.trim().equals("")
                    && imgReplaceTargetDomainName !=null && !imgReplaceTargetDomainName.trim().equals("")){
                Object result = invocation.proceed();
                if(result != null){
                    StringBuilder stringBuilder = new StringBuilder();
                    ArrayList resultList = (ArrayList) result;
                    resultList.forEach(e->{
                        if(e !=null){
                            Class classObj = e.getClass();
                            if(classObj.getSuperclass().equals(BaseEntity.class)){
                                List<Field> allList = new ArrayList<>();
                                //获取对象本身的所有的属性
                                allList.addAll(Arrays.asList(classObj.getDeclaredFields()));
                                //获取BaseEntity对象中属性
                                allList.addAll(Arrays.asList(classObj.getSuperclass().getDeclaredFields()));
                                //以是否包含ImgAttr注解为条件分组
                                Map<Boolean,List<Field>> fieldMap = allList.stream().collect(Collectors.groupingBy(f->f.isAnnotationPresent(ImgAttr.class)));
                                String[] arr = imgReplaceOldDomainName.split(",");
                                //如果有包含ImgAttr的属性
                                if(fieldMap.get(true) !=null){
                                    fieldMap.get(true).forEach(field->{
                                        try {
                                            //获取属性名
                                            String fieldName = field.getName();
                                            //获取属性get、set方法的后缀
                                            String methodSuffix = Character.toUpperCase(fieldName.charAt(0))+fieldName.substring(1);
                                            //获取属性的get方法
                                            Method method = classObj.getMethod("get"+methodSuffix);
                                            //调用get方法得到属性的值
                                            Object value = method.invoke(e);
                                            //只有属性的值不为空，且是String类型的
                                            if(value !=null && value instanceof String){
                                                String oldValue = (String)value;
                                                String urfValue = "";
                                                boolean utfStatus = false;
                                                if(oldValue.toUpperCase().endsWith("%7D") && oldValue.toUpperCase().startsWith("%7B")){
                                                    utfStatus = true;
                                                    urfValue = URLDecoder.decode((String)value,"utf-8");
                                                }else{
                                                    urfValue = oldValue;
                                                }
                                                //解码
                                                stringBuilder.append(urfValue);
                                                //执行替换
                                                this.handleReplace(arr,stringBuilder);
                                                //替换完成后，把替换后的内容重新设置到属性中
                                                method = classObj.getMethod("set"+methodSuffix,String.class);

                                                if(utfStatus){//如果有过解码，则重新编码
                                                    method.invoke(e, URLEncoder.encode(stringBuilder.toString(),"utf-8"));
                                                }else{//如果没有过解码，则不需要重新编码
                                                    method.invoke(e,stringBuilder.toString());
                                                }
                                                stringBuilder.delete(0,stringBuilder.length());
                                            }
                                        } catch (Exception e1) {
                                            e1.printStackTrace();
                                        }
                                    });
                                }
                                try {
                                    BaseEntity baseEntity = (BaseEntity) e;
                                    Map<String,Object> imageMap = baseEntity.getImageMap();
                                    if(imageMap!=null){
                                        imageMap.forEach((k,v)->{
                                            if(v instanceof Map){
                                                ((Map<String,Object>)v).forEach((k1,v1)->{
                                                    stringBuilder.append(v1);
                                                    this.handleReplace(arr,stringBuilder);
                                                    ((Map<String, Object>) v).put(k1,stringBuilder.toString());
                                                    stringBuilder.delete(0,stringBuilder.length());
                                                });
                                            }else{
                                                if(v instanceof  String && k.equals("url")){
                                                    stringBuilder.delete(0,stringBuilder.length());
                                                    stringBuilder.append(v.toString());
                                                    this.handleReplace(arr,stringBuilder);
                                                    imageMap.put(k,stringBuilder.toString());
                                                    stringBuilder.delete(0,stringBuilder.length());
                                                }
                                            }
                                        });
                                    }
                                } catch (Exception e1) {
                                    logger.error("classObj.getSuperclass().getDeclaredField(\"imageMap\")--error");
                                }
                            }
                        }
                    });
                }
            }
        }catch (Exception e){
            logger.error("---replaceImgUrl()--error");
        }
    }

    /**
     * 处理替换
     * @param arr
     * @param stringBuilder
     */
    private void handleReplace(String[] arr, StringBuilder stringBuilder){
        for(String oldUrl : arr){
            if(!oldUrl.trim().equals("")){
                String _val = stringBuilder.toString().replaceAll(oldUrl.trim(),imgReplaceTargetDomainName);
                stringBuilder.delete(0,stringBuilder.length());
                stringBuilder.append(_val);
            }
        }
    }

    /**
     * 获取新的映射数据
     * @param mappedStatement 原有的映射数据
     * @param boundSql 原有的 BoundSql
     * @param newSql 新的SQL
     * @param parameterObject 原有的查询参数
     * @return 返回新的MappedStatement
     */
    private MappedStatement getMappedStatement(MappedStatement mappedStatement,BoundSql boundSql,String newSql,Object parameterObject){
        BoundSql newBoundSql = getBoundSqlWrapper(mappedStatement,boundSql,newSql,parameterObject);
        //得到新的mappedStatement
        MappedStatement newMappedStatement = MappedStatementUtils.copyFromMappedStatement(mappedStatement,new BoundSqlSqlSource(newBoundSql),mappedStatement.getResultMaps());
        return newMappedStatement;
    }

    /**
     * 获取新的SQL合并器
     * @param mappedStatement
     * @param boundSql
     * @param newSql
     * @return
     */
    private BoundSql getBoundSqlWrapper(MappedStatement mappedStatement,BoundSql boundSql,String newSql,Object parameterObject){
        BoundSql _boundSql = new BoundSql(mappedStatement.getConfiguration(),newSql,boundSql.getParameterMappings(),parameterObject);
        return _boundSql;
    }



    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
